#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>

#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>

#include <arpa/inet.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include <netdb.h>

#define	DEFDATALEN	(64-8)
#define	MAXIPLEN	60
#define	MAXICMPLEN	76
#define	MAXPACKET	(65536 - 60 - 8)

void error(std::string msg)
{
	msg = "ERROR! " + msg;
    perror(msg.c_str());
    exit(1);
}

uint16_t in_cksum(uint16_t *addr, unsigned len)
{
  uint16_t answer = 0;
  /*
   * Our algorithm is simple, using a 32 bit accumulator (sum), we add
   * sequential 16 bit words to it, and at the end, fold back all the
   * carry bits from the top 16 bits into the lower 16 bits.
   */
  uint32_t sum = 0;
  while (len > 1)  {
    sum += *addr++;
    len -= 2;
  }

  // mop up an odd byte, if necessary
  if (len == 1) {
    *(unsigned char *)&answer = *(unsigned char *)addr ;
    sum += answer;
  }

  // add back carry outs from top 16 bits to low 16 bits
  sum = (sum >> 16) + (sum & 0xffff); // add high 16 to low 16
  sum += (sum >> 16); // add carry
  answer = ~sum; // truncate to 16 bits
  return answer;
}


int main(int argc, char** argv)
{
    int sock, i, cc, packlen, datalen = DEFDATALEN;

	struct hostent *hp;
	struct sockaddr_in *to, *from, *fr;
	struct protoent	*proto;
	struct ip *ip, *ip_kamikaze;
	u_char * packet, outpack[MAXPACKET];
	char hnamebuf[MAXHOSTNAMELEN];
	std::string hostname;
	std::string target;
	struct icmp *icp;
	struct timeval start, end;
	fd_set rfds;
	struct timeval tv;
	bool cont = true;
	int retval;
	int ret, fromlen, hlen;
	int start_t, end_t;

	if (argc < 2)
		error("No target host provided");

	target = argv[1];

	fr = new sockaddr_in();

	fr->sin_family = AF_INET;
	fr->sin_addr.s_addr = inet_addr("127.0.0.1");

	to = new sockaddr_in();

	to->sin_family = AF_INET;
	to->sin_addr.s_addr = inet_addr(target.c_str());
	if (to->sin_addr.s_addr != (u_int)-1)
		hostname = target;
	else
	{
		hp = gethostbyname(target.c_str());
		if (!hp)
			error("Unknown host " + target);

		to->sin_family = hp->h_addrtype;
		bcopy(hp->h_addr, (caddr_t)&to->sin_addr, hp->h_length);
		strncpy(hnamebuf, hp->h_name, sizeof(hnamebuf) - 1);
		hostname = hnamebuf;
	}

	packlen = datalen + MAXIPLEN + MAXICMPLEN;
	if ( (packet = (u_char *)malloc((u_int)packlen)) == NULL)
		error("malloc error");

	if ( (proto = getprotobyname("icmp")) == NULL)
		error("Unknown protocol icmp");

	sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);//proto->p_proto);
			if (sock < 0)
				error("Can't open socket");

	int optval = 1;
	//setsockopt(sock, IPPROTO_RAW /*getprotobyname("icmp")->p_proto*/, IP_HDRINCL, &optval, sizeof(optval));

	int life_time = 1;

	while(true)
	{
		bzero(outpack, MAXPACKET);

		ip_kamikaze = (struct ip *)(outpack);
		ip_kamikaze->ip_dst = to->sin_addr;
		ip_kamikaze->ip_ttl = life_time++;
		ip_kamikaze->ip_src = fr->sin_addr;

		icp = (struct icmp *)(outpack + sizeof(icmp));
		icp->icmp_type = ICMP_ECHO;
		icp->icmp_code = 0;
		icp->icmp_cksum = 0;
		icp->icmp_seq = 12345;
		icp->icmp_id = getpid();

		//cc = datalen + 8;
		cc = sizeof(ip) + sizeof(icmp);

		icp->icmp_cksum = in_cksum((unsigned short *)icp,cc);

		gettimeofday(&start, NULL);

		i = sendto(sock, (char *)outpack, cc, 0, (struct sockaddr*)to, (socklen_t)sizeof(struct sockaddr_in));
		if (i < 0 || i != cc)
		{
			if (i < 0) perror("Sendto error");
			std::cout << "Wrote " << hostname << " " <<  cc << " chars, ret = " << i << std::endl;
		}

		// Watch stdin (fd 0) to see when it has input.
		FD_ZERO(&rfds);
		FD_SET(sock, &rfds);
		// Wait up to one seconds.
		tv.tv_sec = 1;
		tv.tv_usec = 0;

		cont = true;
		while(cont)
		{
			retval = select(sock+1, &rfds, NULL, NULL, &tv);
			if (retval == -1)
				error("In recvfrom");
			else if (retval > 0)
			{
				from = new sockaddr_in();
				fromlen = sizeof(sockaddr_in);

				std::cout << "try to get\n";
				if ( (ret = recvfrom(sock, (char *)packet, packlen, 0,(struct sockaddr *)from, (socklen_t*)&fromlen)) < 0)
					error("Can't recvfrom");
				std::cout << "get "<< ret <<"\n";

				// Check the IP header
				ip = (struct ip *)((char*)packet);
				hlen = sizeof( struct ip );
				if (ret < (hlen + ICMP_MINLEN))
				{
					char str[11];
					sprintf(str, "%d", ret);
					error((std::string)"Packet too short (" + str  + " bytes) from " + hostname);
				}

				// Now the ICMP part
				icp = (struct icmp *)(packet + hlen);
				if (icp->icmp_type == ICMP_ECHOREPLY)
				{
					std::cout << "Recv: echo reply"<< std::endl;
					if (icp->icmp_seq != 12345)
					{
						std::cout << "received sequence # " << icp->icmp_seq << std::endl;
					}
					if (icp->icmp_id != getpid())
					{
						std::cout << "received id " << icp->icmp_id << std::endl;
					}
				}
				else
					std::cout << "Recv: not an echo reply" << std::endl;

				cont = false;

				gettimeofday(&end, NULL);
				end_t = 1000000*(end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec);

				if(end_t < 1)
					end_t = 1;

				std::cout << "Elapsed time = " << end_t << " usec" << std::endl;
				//return end_t;
			}
			else
			{
				std::cout << "No data within one seconds.\n";
				cont = false;
				//return 0;
			}
		}
	}

	return 0;
}
